package com.wlf.server.common.ws;

import cn.hutool.core.util.StrUtil;
import com.wlf.server.common.config.WsConfig;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@ChannelHandler.Sharable
@Component
public class NioWebSocketHandler extends SimpleChannelInboundHandler<WebSocketFrame> {
    @Resource
    private WsConfig wsConfig;
    @Resource
    private NioWebSocketChannelPool webSocketChannelPool;

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.debug("客户端连接：{}", ctx.channel().id());
        webSocketChannelPool.addChannel(ctx.channel());
        super.channelActive(ctx);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.debug("客户端断开连接：{}", ctx.channel().id());
        webSocketChannelPool.removeChannel(ctx.channel());
        super.channelInactive(ctx);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.channel().flush();
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) {
        // 根据请求数据类型进行分发处理
        if (frame instanceof PingWebSocketFrame pingWebSocketFrame) {
            pingWebSocketFrameHandler(ctx, pingWebSocketFrame);
        } else if (frame instanceof TextWebSocketFrame textWebSocketFrame) {
            textWebSocketFrameHandler(ctx, textWebSocketFrame);
        } else if (frame instanceof CloseWebSocketFrame closeWebSocketFrame) {
            closeWebSocketFrameHandler(ctx, closeWebSocketFrame);
        }
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //log.info("客户端请求数据类型：{}", msg.getClass());
        if (msg instanceof FullHttpRequest) {
            fullHttpRequestHandler(ctx, (FullHttpRequest) msg);
        }
        super.channelRead(ctx, msg);
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        super.userEventTriggered(ctx, evt);

        if (evt instanceof IdleStateEvent event) {
            // 强转一下
            switch (event.state()) {
                case READER_IDLE -> log.info("READER_IDLE" + "空闲");
                case WRITER_IDLE -> log.info("WRITER_IDLE" + "空闲");
                case ALL_IDLE -> log.info("ALL_IDLE" + "空闲");
            }
            log.info(ctx.channel().remoteAddress() + "-----超时事件--" + event.state().name());
            ctx.channel().close();
        }
    }

    /**
     * 处理连接请求，客户端WebSocket发送握手包时会执行这一次请求
     *
     * @param ctx     上下文
     * @param request 握手信息
     */
    private void fullHttpRequestHandler(ChannelHandlerContext ctx, FullHttpRequest request) {
        String uri = request.uri();
        Map<String, String> params = getParams(uri);
        log.info("客户端请求参数：{}", params);
        // 判断请求路径是否跟配置中的一致
        if (wsConfig.getUrl().equals(getBasePath(uri))) {
            // 因为有可能携带了参数，导致客户端一直无法返回握手包，因此在校验通过后，重置请求路径
            request.setUri(wsConfig.getUrl());
        } else {
            ctx.close();
        }
        String userId = params.get("user_id");
        if (StrUtil.isBlank(userId)){
            log.info("用户ID为空，无法登录。");
            return;
        }
        webSocketChannelPool.bindUser(userId, ctx.channel());
    }

    /**
     * 客户端发送断开请求处理
     *
     * @param ctx   上下文
     * @param frame 关闭消息
     */
    private void closeWebSocketFrameHandler(ChannelHandlerContext ctx, CloseWebSocketFrame frame) {
        log.debug(frame.reasonText());
        ctx.close();
    }

    /**
     * 创建连接之后，客户端发送的消息都会在这里处理
     *
     * @param ctx   上下文
     * @param frame 字符串消息
     */
    private void textWebSocketFrameHandler(ChannelHandlerContext ctx, TextWebSocketFrame frame) {
        //客户端发送过来的内容不进行业务处理，原样返回,一般不做处理
        log.debug("收到客户端信息-channelId: {}, 消息内容: {}", ctx.channel().id(), frame.text());
    }

    /**
     * 处理客户端心跳包
     *
     * @param ctx   上下文
     * @param frame 心跳消息
     */
    private void pingWebSocketFrameHandler(ChannelHandlerContext ctx, PingWebSocketFrame frame) {
        ctx.channel().writeAndFlush(new PongWebSocketFrame(frame.content().retain()));
    }


    /**
     * 将路径参数转换成Map对象，如果路径参数出现重复参数名，将以最后的参数值为准
     *
     * @param uri 传入的携带参数的路径
     * @return 参数Map
     */
    private Map<String, String> getParams(String uri) {
        Map<String, String> params = new HashMap<>(10);
        int idx = uri.indexOf("?");
        if (idx != -1) {
            String[] paramsArr = uri.substring(idx + 1).split("&");

            for (String param : paramsArr) {
                idx = param.indexOf("=");
                params.put(param.substring(0, idx), param.substring(idx + 1));
            }
        }

        return params;
    }

    /**
     * 获取URI中参数以外部分路径
     *
     * @param uri url
     * @return path
     */
    private String getBasePath(String uri) {
        if (uri == null || uri.isEmpty())
            return null;

        int idx = uri.indexOf("?");
        if (idx == -1)
            return uri;

        return uri.substring(0, idx);
    }
}
